/**
 * @fileOverview
 */

Karamu = Karamu || {};
Karamu.Controllers = Karamu.Controllers || {};

/**
 * Groups are displayed as categories in desktop view; as tabs in dashboard view. Tabs are sub-divided into columns, and
 * gadgets are stacked in the columns.
 *
 * @class
 * @requires jQuery, underscore.js, karamu.js
 * @param {Object} context the top-level controller object, e.g karamu
 */
Karamu.Controllers.Group = function(context){
    this.context = context;

    /**
     * @param {(Object|string)} element a jQuery object or jQuery selector for a single object to render into
     * @param {Object} options
     * @param {string} options.id must be unique to this view, within this group and across all groups
     * @param {string} options.title anything goes
     * @param {Array} options.layout array of integers, e.g. [33, 33, 33]; number of columns is inferred from the length of this array
     * @param {Array} options.gadgets simple gadget objects (not full-fledged shindig gadget objects)
     * @public
     */
    this.render = function(element, options) {
        this.element = $(element);
        this.options = $.extend(true, {}, this.context.models[this.context.view]._group, options);
        if (this.options.layout.length > options.layout.length) {
            this.options.layout.splice(options.layout.length);
        }

        // if options is nothing but an object with an id, it's a new group, so add it to the model and cloud
        // this.context.model.groups.push(this.options);
        // this.context.group.push(this.options);

        // Create the tab and pane
        var tab = this.context.tmpl('dashboard-tab', {
                id: this.options.id,
                title: this.options.title
            }),
            pane = this.context.tmpl('dashboard-tab-pane', {
                id: this.options.id
            });

        // Hook up the tab icons and title editing behaviors; hide/show states of the icons and inputs are done in CSS
        $('.tab-remove'          , tab).on({click: this.removeElement}, this);
        $('.tab-check button'    , tab).on({click: this.rename       }, this);
        $('.tab-check .cancel'   , tab).on({click: this.cancel       }, this);
        $('.tab-edit'            , tab).on({click: this.edit         }, this);
        $('.tab-name-input input', tab).on({
            keyup: function(e){
                if (e.which == 13) {
                    // capture the "enter" or "return" keystroke
                    e.data.rename(e);
                }
                if (e.which == 27) {
                    // capture the "escape" keystroke
                    e.data.cancel;
                }
            }
        }, this);

        // Add tab and pane to the DOM
        $('a[href="#newTab"]', this.element).parent().before(tab);
        $('.tab-content', this.element).append(pane);

        // Create the columns
        $('.row-fluid', pane).empty();
        _.each(this.options.layout, function(width, index) {
            var gridspan = 'span12';
            switch (width) {
                case  '25': gridspan =  'span3'; break;
                case  '33': gridspan =  'span4'; break;
                case  '50': gridspan =  'span6'; break;
                case  '66':
                case  '67': gridspan =  'span8'; break;
                case  '75': gridspan =  'span9'; break;
                case '100': gridspan = 'span12'; break;
            };
            var column = this.context.tmpl('dashboard-tab-pane-column', {
                width: gridspan,
                column: index + 1
            });

            // Drag'n'dropify them as dropzones
            column.on({
                dragover: this.dragover,
                drop: this.drop
            }, this);

            column.appendTo(pane.children('.row-fluid'));
        }, this);

        // Render each gadget in the column it wants; if the column doesn't exist, put it in column 1
        _.each(this.options.gadgets, function(gadget) {
            var whichColumn = gadget.column <= this.options.layout.length ? gadget.column : 1;
            this.context.controllers.gadget.render($('*[data-column="' + whichColumn + '"]', pane), gadget, this.context);
        }, this);

        // Switch to the tab
        $('a', tab).tab('show');
    };

    /**
     * @param {?Object} e event object
     * @param {?Object} e.data usually a controller object, e.g karamu; if omitted, this is used
     * @public
     */
    this.edit = function(e) {
        e.preventDefault();
        var el = $($(e.currentTarget).parents('li')),
            title = $('.tab-name', el).text();

        $('.tab-name-input input', el).val(title)
        el.addClass('edit-mode');

        // Don't allow dragging of tab while editing title
        el.on({dragstart: this.fail});
    };

    /**
     * @param {?Object} e event object
     * @param {?Object} e.data usually a controller object, e.g karamu; if omitted, this is used
     * @public
     */
    this.rename = function(e) {
        e.preventDefault();
        var el = $($(e.currentTarget).parents('li')),
            id = el.attr('id').substr('tab-'.length),
            title = $.trim($('.tab-name-input input', el).val());

        // TODO: Better validation. This just checks for empty strings or all spaces.
        if (title.length > 0 && !/^\s+$/.test(title)) {
            $('.tab-name', el).text(title);
            $(e.data.context.models[e.data.context.view]).trigger('change-group', [id, {title: title}]);
        }

        el.removeClass('edit-mode');

        // Re-enable dragging of tab
        el.off({dragstart: this.fail});
    };

    /**
     * @param {?Object} e event object
     * @param {?Object} e.data usually a controller object, e.g karamu; if omitted, this is used
     * @public
     */
    this.cancel = function(e) {
        e.preventDefault();
        var el = $($(e.currentTarget).parents('li'));

        el.removeClass('edit-mode');

        // Re-enable dragging of tab
        el.off({dragstart: this.fail});
    };

    /**
     * @param {?Object} e event object
     * @public
     */
    this.fail = function(e){
        return false;
    }

    /**
     * @param {?Object} e event object
     * @param {?Object} e.data usually a controller object, e.g karamu; if omitted, this is used
     * @public
     */
    this.removeElement = function(e) {
        e.preventDefault();
        var el = $($(e.currentTarget).parents('li')),
            id = el.attr('id').substr('tab-'.length);

        // Remove the tab from the interface
        $('#tab-' + id).remove();
        $('#tab-pane-' + id).remove();

        // Update the model
        $(e.data.context.models[e.data.context.view]).trigger('remove-group', id);
    };

    /**
     * @param {Array} list
     * @param {number} column
     * @param {Object} context the top-level controller object, e.g karamu
     */
    this.sort = function(list, column, context) {
        var order = _.map($(context.controllers.catalog.options.dropzoneSelector + '[data-column="' + column + '"] ' + context.controllers.catalog.options.gadgetSelector),
            function(g){ return $(g).attr('id').substr('gadget-'.length); }
        );
        return _.sortBy(list, function(g){
            return _.indexOf(order, g.id);
        });
    };

    /**
     * @param {?Object} e event object
     * @public
     */
    this.dragover = function(e) {
        e.preventDefault();
        e.originalEvent.dataTransfer.effectAllowed = 'copyMove';
    };

    /**
     * @param {?Object} e event object
     * @public
     */
    this.drop = function(e) {
        e.stopPropagation();

        var groupId = $(this).parents('.tab-pane').attr('id').substr('tab-pane-'.length),
            gadgetData = JSON.parse(e.originalEvent.dataTransfer.getData('Text')),
            gadgetUrl = e.originalEvent.dataTransfer.getData('URL');

        // The gadget is coming from the gadget store, and is therefore new; capture all properties
        if (gadgetUrl && gadgetUrl.length > 0) {
            e.originalEvent.dataTransfer.dropEffect = 'copy';

            var gadget = $.extend(true, {}, e.data.context.models[e.data.context.view]._gadget, {
                url: e.originalEvent.dataTransfer.getData('URL'),
                height: gadgetData.height,
                title: gadgetData.title,
                thumbnail: gadgetData.thumbnail,
                column: $(this).attr('data-column')
            });

            e.data.context.getGUID({
                context: e.data.context,
                success: function(xml){
                    gadget.id = $('guid', xml).text();
                },
                error: function() {
                    if (console && console.log) console.log('Error retrieving GUID.');
                },
                complete: function() {
                    // Render, then update the model in the background
                    this.controllers.gadget.render(e.currentTarget, gadget, this);
                    $(this.models[this.view]).trigger('add-gadget', [gadget, groupId]);
                }
            });
        } else {
            e.originalEvent.dataTransfer.dropEffect = 'move';

            var gadget = {
                column: $(this).attr('data-column')
            };

            // Render, then update the model in the background
            e.data.context.controllers.gadget.rerender(this, $('#gadget-' + gadgetData.id));
            $(e.data.context.models[e.data.context.view]).trigger('change-gadget', [gadgetData.id, gadget, groupId]);
        }

        return false;
    };

    /**
     * @param {Object} e event object
     * @param {?Object} e.data usually a controller object, e.g karamu; if omitted, this is used
     */
    this.addNewTab = function(e){
        e.preventDefault();
        var group = $.extend(true, {}, e.data.models[e.data.view]._group);
        e.data.getGUID({
            context: e.data,
            success: function(xml){
                group.id = $('guid', xml).text();
            },
            error: function(xhr, status, err){
                if (console && console.log) console.log('Error retrieving GUID.');
            },
            complete: function(xhr, status){
                // Render, then update the model in the background
                this.controllers.group.render('#dashboard-tabs', group, this);
                $(this.models[this.view]).trigger('add-group', group);
            }
        });
    };

    /**
     * @param {Object} e event object
     * @param {Object} e.data the top-level controller object, e.g karamu
     * @param {Object} group a plain object
     * @public
     */
    this.add = function(e, group){
        e.data.models[e.data.view].groups.push(group);
        e.data.controllers.group.push({
            context: e.data,
            data: group,
            groupId: group.id,
            success: function(){
                if (console && console.log) console.log('Group "' + group.title + '" (' + group.id + ') added.');
                $(this.models[this.view]).trigger('change-groups');
            }
        });
    };

    /**
     * @param {Object} e event object
     * @param {Object} e.data the top-level controller object, e.g karamu
     * @param {string} groupId exactly what it sounds like
     * @param {?Object} groupData a plain object; if present, selective data substitution occurs
     * @public
     */
    this.change = function(e, groupId, groupData){
        for (var a = 0, b = e.data.models[e.data.view].groups.length; a < b; a++) {
            if (e.data.models[e.data.view].groups[a].id === groupId) {
                if (groupData) {
                    for (f in groupData) {
                        e.data.models[e.data.view].groups[a][f] = groupData[f];
                    }
                }
                var changedGroup = $.extend(true, {}, e.data.models[e.data.view].groups[a]);
                e.data.controllers.group.push({
                    context: e.data,
                    data: changedGroup,
                    groupId: changedGroup.id,
                    success: function(){
                        if (console && console.log) console.log('Group "' + changedGroup.title + '" (' + changedGroup.id + ') changed.');
                        $(this.models[this.view]).trigger('change-groups');
                    }
                });
            }
        }
    };

    /**
     * @param {Object} e event object
     * @param {Object} e.data the top-level controller object, e.g karamu
     * @param {string} groupId exactly what it sounds like
     * @public
     */
    this.remove = function(e, groupId){
        for (var a = 0, b = e.data.models[e.data.view].groups.length; a < b; a++) {
            if (e.data.models[e.data.view].groups[a].id === groupId) {
                e.data.models[e.data.view].groups.splice(a, 1);
                e.data.controllers.group.trash({
                    context: e.data,
                    groupId: groupId,
                    success: function(){
                        if (console && console.log) console.log('Group (' + groupId + ') deleted.');
                        $(this.models[this.view]).trigger('change-groups');
                    }
                });
                b--;
            }
        }
    };

    /**
     * Resizes the 'dropzone' columns to a different layout in the active tab
     *
     * @param e {object} event object
     */
    this.changeLayout = function(e){
        e.preventDefault();
        var groupId = $(e.data.controllers.catalog.options.dropzoneSelector).parents('.tab-pane').attr('id').substr('tab-pane-'.length);
        for (var a = 0, b = e.data.models[e.data.view].groups.length; a < b; a++) {
            if (e.data.models[e.data.view].groups[a].id === groupId) {
                e.data.models[e.data.view].groups[a].layout = $(e.currentTarget).attr('data-layout').split('|');
                $('#tab-' + groupId).remove();
                $('#tab-pane-' + groupId).remove();
                e.data.controllers.group.render('#dashboard-tabs', e.data.models[e.data.view].groups[a], e.data);
                if (console && console.log) console.log('Layout of group "' + e.data.models[e.data.view].groups[a].title + '" (' + groupId + ') updated.');
                $(e.data.models[e.data.view]).trigger('change-group', groupId);
            }
        }
    };

    /**
     * @param {?Object} args
     * @param {?Function} args.complete
     * @param {?Object} args.context the top-level controller object, e.g karamu; if omitted, the XHR object is used
     * @param {?Function} args.error
     * @param {?Function} args.success
     * @param {string} args.groupId
     * @public
     */
    this.fetch = function(args){
        $.ajax({
            cache: false,
            complete: args.complete,
            context: args.context,
            dataType: 'json',
            error: args.error,
            success: args.success,
            type: 'GET',
            url: args.context.services.group.replace('%data%', args.groupId)
        });
    };

    /**
     * @param {?Object} args
     * @param {?Function} args.complete
     * @param {?Object} args.context the top-level controller object, e.g karamu; if omitted, the XHR object is used
     * @param {?Object} args.data
     * @param {?Function} args.error
     * @param {?Function} args.success
     * @param {string} args.groupId
     * @public
     */
    this.push = function(args){
        $.ajax({
            cache: false,
            complete: args.complete,
            contentType: 'application/json',
            context: args.context,
            data: JSON.stringify(args.data),
            error: args.error,
            success: args.success,
            type: 'PUT',
            url: args.context.services.group.replace('%data%', args.groupId)
        });
    };

    /**
     * @param {?Object} args
     * @param {?Function} args.complete
     * @param {?Object} args.context the top-level controller object, e.g karamu; if omitted, the XHR object is used
     * @param {?Function} args.error
     * @param {?Function} args.success
     * @param {string} args.groupId
     * @public
     */
    this.trash = function(args){
        $.ajax({
            cache: false,
            complete: args.complete,
            context: args.context,
            error: args.error,
            success: args.success,
            type: 'DELETE',
            url: args.context.services.group.replace('%data%', args.groupId)
        });
    };

};